package cn.xzqwjw.taskmanager.security;

import cn.xzqwjw.taskmanager.domain.pojo.SysPurview;
import cn.xzqwjw.taskmanager.domain.pojo.SysRole;
import cn.xzqwjw.taskmanager.service.SysPurviewService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

/**
 * 自定义的元数据源类，用来提供鉴权过程中，访问资源（Url）所需的角色（权限）
 *
 * @author rush
 */
@Component
public class AuthMetadataSource implements FilterInvocationSecurityMetadataSource {

  // reader 的相关url未启用

  private static final String URL_LOGIN_ADMIN = "/api/admin/login";
  private static final String URL_LOGOUT_ADMIN = "/api/admin/logout";

  private final SysPurviewService purviewService;

  @Autowired
  public AuthMetadataSource(SysPurviewService purviewService) {
    this.purviewService = purviewService;
  }

  /**
   * 本方法返回访问资源（通常是某个url）所需的角色和权限的集合
   */
  @Override
  public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
    // FilterInvocation （过滤器调用） 的作用是将 doFilter 传进来的
    // request,response 和 FilterChain 对象保存起来
    // 用于提供给 FilterSecurityInterceptor 的处理代码调用
    // 因此，使用 getRequestUrl 方法就得到了需要访问的资源，即网址
    String requestUrl = ((FilterInvocation) object).getRequestUrl();
    HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
    String httpMethod = request.getMethod();
    // 如果要访问的url是登录登出url则返回空值，后面的鉴权方法会做判断
    if (URL_LOGIN_ADMIN.equals(requestUrl) || URL_LOGOUT_ADMIN.equals(requestUrl)) {
      return null;
    }
    // 从数据库中得到所有权限（权限包含资源）
    List<SysPurview> allPurviewList = purviewService.list();
    AntPathMatcher antPathMatcher = new AntPathMatcher();
    // 遍历数据库里所有权限
    for (SysPurview purview : allPurviewList) {
      // requestUrl 请求访问的地址资源
      // purview.getUrl() 数据库里存的权限表里的地址资源
      // 如果现在要访问的地址以及请求方法 和 数据库里规定的某个地址所需要的权限和请求方法相匹配
      // 说明要访问的地址是需要验证权限的，那么将拥有这条权限的所有角色和这条权限本身放入一个数组里返回
      if (antPathMatcher.match(purview.getServerUrl(), requestUrl) &&
          httpMethod.equalsIgnoreCase(purview.getHttpMethod())) {
        // 如果 请求访问的地址资源 和 数据库里存的权限表里的地址资源 相等
        // 注意：sysPurviewService.list()方法得到的权限列表里的权限对象是空的
        // 因为在 mapper.xml 里定义的是 lazy 的加载模式
        // 必须重新用 getById 的方式读取到这个权限所对应的角色列表和管理员列表
        purview = purviewService.getById(purview.getId());
        // 如果该权限所属角色列表大于0（意味着这个权限属于某些角色）
        // 比如 /product/add 和某条权限 /product/** 匹配，并且这个权限有两个角色拥有
        if (Objects.nonNull(purview.getRoleList()) && !purview.getRoleList().isEmpty()) {
          // 得到此权限所属的角色列表
          List<SysRole> sysRoleList = purview.getRoleList();
          // 将角色列表里每个角色的 RoleCode 存到一个数组里
          // 这个数组的长度要多一位，是为了给后面的权限code留个位置
          String[] values = new String[sysRoleList.size() + 1];
          for (int i = 0; i < sysRoleList.size(); i++) {
            values[i] = sysRoleList.get(i).getCode();
          }
          // 把权限的code也加进去
          values[values.length - 1] = purview.getCode();
          // 返回要访问的链接需要哪些角色和权限的数组
          return SecurityConfig.createList(values);
        }
      }
    }
    // 经过上面循环后，执行到这里，说明要访问的网址还没有在数据库里设置权限
    // 但是仍然是需要登录访问的（这是为了保证安全）
    // 所以这里就返回一个特殊的ROLE_LOGIN，以便后面的比对
    return SecurityConfig.createList("ROLE_NEED_LOGIN");
  }

  @Override
  public Collection<ConfigAttribute> getAllConfigAttributes() {
    return null;
  }

  @Override
  public boolean supports(Class<?> clazz) {
    return FilterInvocation.class.isAssignableFrom(clazz);
  }

}
